package httpclient

import (
	"bytes"
	"context"
	"crypto/tls"
	"io"
	"net/http"
	"net/url"
	"time"

	_ctx "github.com/romapres2010/goapp/pkg/common/ctx"
	_err "github.com/romapres2010/goapp/pkg/common/error"
	_httplog "github.com/romapres2010/goapp/pkg/common/httplog"
	_http "github.com/romapres2010/goapp/pkg/common/httpservice"
	_log "github.com/romapres2010/goapp/pkg/common/logger"
	_metrics "github.com/romapres2010/goapp/pkg/common/metrics"
	neturl "net/url"
)

const HTTP_CLIENT_GET = "GET"
const HTTP_CLIENT_POST = "POST"
const HTTP_CLIENT_PUT = "PUT"
const HTTP_CLIENT_DELETE = "DELETE"

// Header represent temporary HTTP header for save
type Header map[string]string

// Call represent а parameters for client requests
type Call struct {
	URL                string           // URL
	FullURL            string           // 完整的URL，包括参数
	Args               url.Values       // URL字符串的参数
	UserID             string           // 用于认证的用户ID
	UserPwd            string           // 用于认证的用户密码
	CallMethod         string           // 调用的HTTP方法
	CallTimeout        time.Duration    // 调用的完整超时时间
	ContentType        string           // 响应中的内容类型
	InsecureSkipVerify bool             // 忽略证书验证
	ReCallRepeat       int              // 服务不可用时的调用尝试次数 - 0 表示无限制
	ReCallWaitTimeout  time.Duration    // 服务不可用时调用之间的超时时间
	HTTPLogger         *_httplog.Logger // HTTP日志记录服务
}

// Process - represent client common task in process outgoing HTTP request
func (c *Call) Process(ctx context.Context, header Header, requestBody []byte) (statusCode int, responseBuf []byte, respHeader http.Header, err error) {

	var reqID = _http.GetNextHTTPRequestID() //请求的唯一标识符
	var req *http.Request
	var resp *http.Response
	var tic = time.Now()

	if c != nil {

		{ // 输入检查
			if ctx == nil {
				return 0, nil, nil, _err.NewTyped(_err.ERR_INCORRECT_CALL_ERROR, reqID, "if cfg == nil {}").PrintfError()
			}
			if c.URL == "" {
				return 0, nil, nil, _err.NewTyped(_err.ERR_HTTP_CALL_EMPTY_URL, reqID).PrintfError()
			}
		} // 输入检查

		// 添加指标
		_metrics.IncHTTPClientCallCountVec(c.URL, c.CallMethod)

		// 对于每个请求，我们创建一个新的上下文，并在其中保存HTTP请求的唯一编号
		callCtx := _ctx.NewContextHTTPRequestID(ctx, reqID)

		// 向调用添加参数
		if c.Args != nil {
			c.FullURL = c.URL + "?" + url.Values(c.Args).Encode()
		} else {
			c.FullURL = c.URL
		}

		// 为了提供取消的能力，我们创建一个带有上下文的请求
		// 将缓冲区作为请求的一部分传递到请求体中
		if requestBody != nil {
			req, err = http.NewRequestWithContext(callCtx, c.CallMethod, c.FullURL, bytes.NewReader(requestBody))
		} else {
			req, err = http.NewRequestWithContext(callCtx, c.CallMethod, c.FullURL, nil)
		}
		if err != nil {
			return 0, nil, nil, _err.WithCauseTyped(_err.ERR_HTTP_CALL_CREATE_CONTEXT_ERROR, reqID, err).PrintfError()
		}

		// 将头部写入请求
		if header != nil {
			for key, h := range header {
				req.Header.Add(key, h)
			}
		}

		// 添加HTTP基本认证
		if c.UserID != "" && c.UserPwd != "" {
			req.SetBasicAuth(c.UserID, c.UserPwd)
		} else {
			_log.Debug("UserID is null or UserPwd is null. Do call without HTTP Basic Authentication: reqID, Method, URL, len(requestBody)", err, reqID, c.CallMethod, c.FullURL, len(requestBody))
		}

		// 复制默认的传输层
		tr := http.DefaultTransport.(*http.Transport)

		// 在使用SSL时重写无效证书的检查
		tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: c.InsecureSkipVerify}

		// 创建一个带有重写传输层的HTTP客户端
		client := &http.Client{
			Transport: tr,
			Timeout:   c.CallTimeout, // 完整的超时等待时间
		}

		// 如果服务不可用 - 则循环并延迟
		tryCount := 1
		for {
			// 检查是否接收到了关闭上下文的信号 - 停止处理
			select {
			case <-callCtx.Done():
				return 0, nil, nil, _err.NewTyped(_err.ERR_CONTEXT_CLOSED_ERROR, reqID, "Method, URL, len(requestBody)", []interface{}{c.CallMethod, c.FullURL, len(requestBody)}).PrintfError()
			default:

				// 记录发出的HTTP请求
				if c.HTTPLogger != nil {
					_ = c.HTTPLogger.LogHTTPOutRequest(callCtx, req)
				}

				// 执行请求
				resp, err = client.Do(req)

				// 记录进入的HTTP响应
				if c.HTTPLogger != nil {
					_ = c.HTTPLogger.LogHTTPInResponse(callCtx, resp)
				}

				// 处理错误
				if err != nil {
					if httperr, ok := err.(*neturl.Error); ok {
						// 如果因超时而被中断或上下文关闭
						if httperr.Timeout() {
							return 0, nil, nil, _err.WithCauseTyped(_err.ERR_CONTEXT_CLOSED_ERROR, reqID, httperr, c.CallTimeout).PrintfError()
						} else {
							return 0, nil, nil, _err.WithCauseTyped(_err.ERR_HTTP_CALL_OTHER_NETURL_ERROR, reqID, httperr, httperr.Error()).PrintfError()
						}
					}
					return 0, nil, nil, _err.WithCauseTyped(_err.ERR_HTTP_CALL_OTHER_ERROR, reqID, err, err.Error()).PrintfError()
				}

				// 计算响应体
				if resp.Body != nil {
					responseBuf, err = io.ReadAll(resp.Body)
					defer func(Body io.ReadCloser) {
						err := Body.Close()
						if err != nil {
							_ = _err.WithCauseTyped(_err.ERR_COMMON_ERROR, reqID, err, "httpclient.Process -> io.ReadCloser.Close()").PrintfError()
						}
					}(resp.Body)
					if err != nil {
						return 0, nil, nil, _err.WithCauseTyped(_err.ERR_HTTP_CALL_READ_BODY_ERROR, reqID, err, err.Error()).PrintfError()
					}
				}

				// 添加指标
				_metrics.IncHTTPClientCallDurationVec(c.URL, c.CallMethod, time.Now().Sub(tic))

				_log.Debug("Process HTTP call: reqID, Method, URL, len(requestBody), resp.StatusCode, len(responseBuf), duration", reqID, c.CallMethod, c.FullURL, len(requestBody), resp.Status, len(responseBuf), time.Now().Sub(tic))

				// 部分分析响应状态
				if resp.StatusCode == http.StatusNotFound {
					// 如果超过尝试次数则退出
					if c.ReCallRepeat != 0 && tryCount >= c.ReCallRepeat {
						return http.StatusNotFound, nil, resp.Header, _err.NewTyped(_err.ERR_HTTP_CALL_URL_NOT_FOUND_ERROR, reqID, c.ReCallRepeat, c.URL, c.Args).PrintfError()
					}

					// 如果URL不可用 - 在循环中继续
					_log.Info("URL was not found, wait and try again: : reqID, Method, URL, ReCallWaitTimeout, tryCount", reqID, c.CallMethod, c.FullURL, c.ReCallWaitTimeout, tryCount)

					// 进行延迟
					time.Sleep(time.Duration(c.ReCallWaitTimeout))
					tryCount++

					break // 退出到循环的开始
				} else if resp.StatusCode == http.StatusMethodNotAllowed {
					return http.StatusMethodNotAllowed, nil, resp.Header, _err.NewTyped(_err.ERR_HTTP_CALL_METHOD_NOT_ALLOWED_ERROR, reqID, c.CallMethod).PrintfError()
				} else if resp.StatusCode == http.StatusBadRequest {
					_log.Error("Process HTTP call - StatusBadRequest: reqID, Method, URL, len(requestBody), resp.StatusCode, len(responseBuf), duration", reqID, c.CallMethod, c.FullURL, len(requestBody), resp.Status, len(responseBuf), time.Now().Sub(tic))
				}

				// 返回上一级以进一步分析响应
				return resp.StatusCode, responseBuf, resp.Header, nil
			}
		}
	}
	return 0, nil, nil, _err.NewTyped(_err.ERR_INCORRECT_CALL_ERROR, reqID, "c != nil").PrintfError()
}
